package com.lzx.lizhenxing.sys.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.lzx.lizhenxing.common.constrants.CommonConstants;
import com.lzx.lizhenxing.common.exception.CommonException;
import com.lzx.lizhenxing.common.utils.DateUtils;
import com.lzx.lizhenxing.common.utils.JwtUtils;
import com.lzx.lizhenxing.common.utils.RedisUtils;
import com.lzx.lizhenxing.common.utils.UserUtils;
import com.lzx.lizhenxing.delivery.vo.DeliveryPersonInfoVo;
import com.lzx.lizhenxing.point.dao.PointDao;
import com.lzx.lizhenxing.point.enums.PointTypeEnum;
import com.lzx.lizhenxing.point.model.Point;
import com.lzx.lizhenxing.point.utils.PointUtils;
import com.lzx.lizhenxing.sys.dao.UserDao;
import com.lzx.lizhenxing.sys.dao.UserRoleDao;
import com.lzx.lizhenxing.sys.enums.UserKeyEnum;
import com.lzx.lizhenxing.sys.model.User;
import com.lzx.lizhenxing.sys.param.LoginParam;
import com.lzx.lizhenxing.sys.param.RegisterParam;
import com.lzx.lizhenxing.sys.service.UserService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.util.*;

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserDao userDao;

    @Resource
    private UserRoleDao userRoleDao;

    @Resource
    private PointDao pointDao;

    @Resource
    private PointUtils pointUtils;

    @Override
    public String login(LoginParam param) {

        User user = userDao.selectByUsername(param.getUsername());
        if(StringUtils.isBlank(param.getUsername())){
            throw new CommonException(CommonConstants.NO_LOGIN,"请输入账号");
        }
        if(StringUtils.isBlank(param.getPassword())){
            throw new CommonException(CommonConstants.NO_LOGIN,"请输入密码");
        }
        if (user == null) {
            throw new CommonException(CommonConstants.NO_LOGIN,"不存在该用户");
        }
        if(!param.getPassword().equals(user.getPassword())) {
            throw new CommonException(CommonConstants.NO_LOGIN,"密码错误");
        }

        Map<String, Object> map = new HashMap<>();
        map.put("uid", user.getId());
        // jwt已经存了用户信息，jwt可以设置过期时间，实现之前redis存储登录信息的功能
        String jwt = JwtUtils.createJwt(map);
        // 使用jwt做token，一般需要将jwt作为key存到redis中,否则需要在退出时将jwt加入黑名单，操作较为复杂，此处使用redis存储
        RedisUtils.setValue(UserKeyEnum.USER_TOKEN.getKey() + user.getId().toString(), jwt);
        return jwt;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void register(RegisterParam param) {
//        注册时，需保证用户名不能重复，为了保证安全性，可对密码做md5编码
//        根据用户名查询数据库，保证用户名不重复
        User user = userDao.selectByUsername(param.getUsername());
        if(StringUtils.isBlank(param.getPassword())) {
            throw new CommonException(CommonConstants.ERROR,"请输入密码");
        }
        if (!Objects.equals(param.getPassword(), param.getCheckPass())) {
            throw new CommonException(CommonConstants.ERROR,"两次输入的密码不一致");
        }
        if (Objects.nonNull(user)) {
            throw new CommonException(CommonConstants.ERROR,"用户名已存在");
        }
//        否则把用户信息放入数据库
        User registerUser = new User();
        registerUser.setUsername(param.getUsername());
        registerUser.setPassword(param.getPassword());
        registerUser.setRole(param.getRole());
        registerUser.setPhone(param.getPhone());
        userDao.insert(registerUser);
    }

    @Override
    public void logout() {
        // 退出登陆需要将redis的token清空
        Long userId = UserUtils.getUser();
        try{
            RedisUtils.delKey(UserKeyEnum.USER_TOKEN.getKey() + userId.toString());
        }catch (Exception e){
            e.printStackTrace();
            throw new CommonException(CommonConstants.ERROR,"清除redis的token异常，退出失败");
        }
    }

    @Override
    public List<String> getUserInfo() {
        // 从redis中获取用户id
        Long userId = (Long)RedisUtils.getValue(UserKeyEnum.USER_ID.getKey());
        // 两表联查获取权限
        List<String> roles = userRoleDao.selectUserRoleByUserId(userId);
        return roles;
    }

    @Override
    public void updatePoint(Integer uid, Integer point) {

    }

    // 判断当前是否签到
    // 如没有签到，执行签到操作
    // 签到后送积分
    // 判断连续签到天数
    // 根据连续签到天数送积分
    @Transactional(rollbackFor = CommonException.class) // 添加事务
    @Override
    public Integer sign() {
        // 签到获取到的所有积分叠加到用户表的用户积分上
        Long uid = UserUtils.getUser();
        User user = userDao.selectById(uid);
        int totalPoint = user.getPoint() == null ? 0 : user.getPoint();
        // 按月签到、一天只能签到一次、数据存储到bitmap、
        // 签到送积分、进行连续签到天数的判断，一般连续签到指定的天数，会有额外的奖励
        LocalDate localDate = LocalDate.now();
        int day = localDate.getDayOfMonth();
        String yearAndMonth = DateUtils.getYearAndMonth();
        // 签到信息的key  sign:年月:uid
        // 使用bitmap按月存储签到记录，offset为1表示1号，2表示2号，依次类推
        String key = String.format("sign:%s:%s", yearAndMonth, uid);
        boolean ret = RedisUtils.getBit(key, day);
        if (ret) {
            throw new CommonException("今日已签到");
        }
        // 如果没有签到，通过bitmap记录签到信息
        // 注意 即使该方法标注被事务管理，但是redis的操作不能回滚，需要回滚redis操作的话，可以使用lua脚本实现，或者手动回滚
        RedisUtils.setBit(key, day);
        // 送积分
        pointUtils.insertPoint(uid,1, PointTypeEnum.TYPE_SIGN.getType());
        totalPoint += 1;
        // 计算连续签到的天数
        // 通过bitfield取签到数据，取当天及当天之前的签到数据
        Long v = RedisUtils.bitfield(key, day, 1);
        // 将数字转为二进制字符串；使用位运算
        // 获取的值和1做按位与的运算，如果运算结果为1，说明原数据的最后一个bit位为1，说明签到
        // 签到天数+1
        // 然后原数据向右位移1位，继续上述运算，直到结果为0，说明没有签到，退出循环
        // 10111100111
        int continueDays = 0; // 此为连续签到天数
        while ((v & 1) == 1) {
            continueDays++;
            v = v >> 1;
        }
        // 连续签到送的额外积分
        Point continueSignPoint = new Point();
        switch (continueDays) {
            case 3:
                pointUtils.insertPoint(uid,2, PointTypeEnum.TYPE_CONTINUE_SIGN.getType());
                totalPoint += 2;
                break;
            case 5:
                pointUtils.insertPoint(uid,3, PointTypeEnum.TYPE_CONTINUE_SIGN.getType());
                totalPoint += 3;
                break;
            case 10:
                pointUtils.insertPoint(uid,5, PointTypeEnum.TYPE_CONTINUE_SIGN.getType());
                totalPoint += 5;
            default:
                break;
        }
        userDao.updatePointById(uid,totalPoint);
        return totalPoint;
    }

    @Override
    public Integer getPoint() {
        Long uid = UserUtils.getUser();
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getId,uid);
        User user = userDao.selectOne(queryWrapper);
        return user.getPoint() == null ? 0 : user.getPoint();
    }

    @Override
    public List<DeliveryPersonInfoVo> getDeliveryPersonInfo() {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getRole,1);
        // 查找所有配送员
        List<User> users = userDao.selectList(queryWrapper);
        // 根据配送员的好评数与总评数 算出评价
        List<DeliveryPersonInfoVo> voList = new ArrayList<>();
        // 先将数据转换为vo
        users.forEach(e -> {
            DeliveryPersonInfoVo vo = new DeliveryPersonInfoVo();
            BeanUtils.copyProperties(e,vo);
            Integer goodReviews = Objects.isNull(vo.getGoodReviews()) ? 0 : vo.getGoodReviews();
            Integer allReviews = Objects.isNull(vo.getAllReviews()) ? 0 : vo.getAllReviews();
            // 设置好评率
            if(!allReviews.equals(0)){
                double goodReviewRate = goodReviews / allReviews;
                vo.setGoodRate(goodReviewRate);
            }
            voList.add(vo);
        });
        return voList;
    }
}
